﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Collections.Specialized;

namespace JWFW.SFL
{
    /// <summary>
    /// 依据Http协议，向网站发出请求，并获取返回的页面数据。
    /// 允许多线程、保持Cookie、设置代理
    /// 接受普通页面数据、其他格式的文件
    /// </summary>
    public class HttpOP
    {
        /// <summary>
        /// Http操作类，默认编码为GB2312
        /// </summary>
        public HttpOP()
        {
        }

        /// <summary>
        /// 获取实际可访问的URL
        /// </summary>
        /// <param name="link">将要访问的链接：可以是绝对、相对、相对网站根的路径</param>
        /// <param name="baseURL">跳转源的基础路径</param>
        /// <returns>可访问的完整的URL</returns>
        public static string GetActualURL(string link, string baseURL)
        {
            if (link.ToLower().Trim().StartsWith("http") 
                || link.ToLower().Trim().StartsWith("ftp")
                || link.ToLower().Trim().StartsWith("https"))
            {
                return link;
            }
            else if (!link.StartsWith("/"))
            {
                return baseURL + link;
            }
            else
            {
                Uri u = new Uri(baseURL);
                return u.Scheme + Uri.SchemeDelimiter + u.Authority + link;
            }
        }

        public event Action<HttpWebRequest> BeforeRequestHandler;

        private CookieCollection m_requestCookies = new CookieCollection();
        private NameValueCollection m_specialHeaders = new NameValueCollection();
        private CookieCollection m_responseCookies = new CookieCollection();
        private HttpWebResponse m_lastResponse = null;

        private string m_headerAccept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/x-shockwave-flash, */*";
        private string m_headerUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 2.0.50727)";
        private bool m_keepAlive = true;
        private Version m_protocolVersion = HttpVersion.Version11;
        private int m_timeout = 1000 * 60 * 2;
        private string m_referer = "";
        private bool m_expect100Continue = false;
        private Encoding m_encoding = Encoding.GetEncoding("GB2312");
        private bool m_allowAutoRedirect = true;

        private string m_proxy = null;
        private string m_port = null;
        private string m_proxyName = null;
        private string m_proxyPwd = null;

        /// <summary>
        /// 最后一次响应对应的基本URL，用于跳转到其他页面的基础
        /// </summary>
        public string LastResponseBaseURL
        {
            get 
            {
                string url = "";
                if (m_lastResponse != null)
                {
                    url = m_lastResponse.ResponseUri.AbsoluteUri;
                    if (!url.EndsWith("/"))
                    {
                        int pos = url.LastIndexOf("/");
                        url = url.Remove(pos + 1);
                    }
                }
                return url;
            }
        }
        /// <summary>
        /// Cookie容器
        /// </summary>
        public CookieCollection RequestCookies
        {
            get { return m_requestCookies; }
            set { m_requestCookies = value; }
        }
        /// <summary>
        /// 额外特殊的头信息
        /// </summary>
        public NameValueCollection SpecialHeaders
        {
            get { return m_specialHeaders; }
            set { m_specialHeaders = value; }
        }
        /// <summary>
        /// 最后一次接收返回的Cookie
        /// </summary>
        public CookieCollection ResponseCookies
        {
            get { return m_responseCookies; }
            set { m_responseCookies = value; }
        }
        /// <summary>
        /// 本实例最后获取到的Response对象
        /// </summary>
        public HttpWebResponse LastResponse
        {
            get { return m_lastResponse; }
        }
        /// <summary>
        /// 获取或设置 Accept HTTP 标头的值。有默认值。
        /// </summary>
        public string HeaderAccept
        {
            get { return m_headerAccept; }
            set { m_headerAccept = value; }
        }
        /// <summary>
        /// 获取或设置 User-agent HTTP 标头的值。有默认值。
        /// </summary>
        public string HeaderUserAgent
        {
            get { return m_headerUserAgent; }
            set { m_headerUserAgent = value; }
        }
        /// <summary>
        /// 获取或设置一个值，该值指示是否与 Internet 资源建立持久性连接。默认为True。
        /// </summary>
        public bool KeepAlive
        {
            get { return m_keepAlive; }
            set { m_keepAlive = value; }
        }
        /// <summary>
        /// 获取或设置请求的HTTP版本值。默认为Version10。
        /// </summary>
        public Version ProtocolVersion
        {
            get { return m_protocolVersion; }
            set { m_protocolVersion = value; }
        }
        /// <summary>
        /// 获取或设置请求的超时值。默认为120秒。
        /// </summary>
        public int Timeout
        {
            get { return m_timeout; }
            set { m_timeout = value; }
        }
        /// <summary>
        /// 发送数据的编码，默认为GB2312
        /// </summary>
        public Encoding Encoding
        {
            get { return m_encoding; }
            set { m_encoding = value; }
        }
        /// <summary>
        /// 获取或设置：设定Client 和 Server在Post数据前需要进行 ‍“请求头域” 的数据匹配，相当于是握手。如果匹配则开始进行body 的内容，Post数据。否则，报错（417） Unkown。默认为False。
        /// </summary>
        public bool Expect100Continue
        {
            get { return m_expect100Continue; }
            set { m_expect100Continue = value; }
        }
        /// <summary>
        /// 引用页，当访问禁止盗链的资源时，需要设置这个值为网站的URL
        /// </summary>
        public string Referer
        {
            get { return m_referer; }
            set { m_referer = value; }
        }
        /// <summary>
        /// 获取或设置是否自动跳转
        /// </summary>
        public bool AllowAutoRedirect
        {
            get { return m_allowAutoRedirect; }
            set { m_allowAutoRedirect = value; }
        }

        /// <summary>
        /// 代理服务器
        /// </summary>
        public string Proxy
        {
            get { return m_proxy; }
            set { m_proxy = value; }
        }
        /// <summary>
        /// 代理服务器的端口号
        /// </summary>
        public string Port
        {
            get { return m_port; }
            set { m_port = value; }
        }
        /// <summary>
        /// 登录到代理服务器的用户名
        /// </summary>
        public string ProxyName
        {
            get { return m_proxyName; }
            set { m_proxyName = value; }
        }
        /// <summary>
        /// 登录到代理服务器的密码
        /// </summary>
        public string ProxyPwd
        {
            get { return m_proxyPwd; }
            set { m_proxyPwd = value; }
        }

        /// <summary>
        /// 访问网络，并获得响应数据流，不保存Cookie到实例
        /// </summary>
        /// <param name="url">网页的URL</param>
        /// <param name="postData">post方式提交时的数据</param>
        /// <param name="request">外部定义的HttpWebRequest，本实例定义的Request参数将对其失效</param>
        /// <returns>网页的响应数据流</returns>
        public HttpWebResponse Navigate(string url, string postData, HttpWebRequest request = null)
        {
            return Navigate(url, GetPostDataBytes(postData), request);
        }
        /// <summary>
        /// 访问网络，并获得响应数据流，不保存Cookie到实例
        /// </summary>
        /// <param name="url">网页的URL</param>
        /// <param name="postData">post方式提交时的数据</param>
        /// <param name="request">外部定义的HttpWebRequest，本实例定义的Request参数将对其失效</param>
        /// <returns>网页的响应数据流</returns>
        public HttpWebResponse Navigate(string url, byte[] postData = null, HttpWebRequest request = null)
        {
            if (request == null)
            {
                request = (HttpWebRequest)HttpWebRequest.Create(url);
                request.Accept = m_headerAccept;
                request.UserAgent = m_headerUserAgent;
                request.Referer = m_referer;
                request.KeepAlive = m_keepAlive;
                request.AllowAutoRedirect = m_allowAutoRedirect;
                request.ProtocolVersion = m_protocolVersion;
                request.Timeout = m_timeout;
                request.ServicePoint.Expect100Continue = false;
                request.Headers.Add(m_specialHeaders);
            }
            SetCookies(url, request);
            SetProxy(request);
            SetPostData(postData, request);

            if (BeforeRequestHandler != null)
            {
                //处理订阅的请求前事件
                BeforeRequestHandler(request);
            }

            m_lastResponse = (HttpWebResponse)request.GetResponse();
            return m_lastResponse;
        }
        /// <summary>
        /// 访问网页，获取字符串形式的返回信息，并保存Cookie到实例
        /// </summary>
        /// <param name="url">网页的URL</param>
        /// <param name="postData">post方式提交时的数据</param>
        /// <returns>网页的源代码</returns>
        public string NavigateString(string url, byte[] postData = null)
        {
            string res = "";
            try
            {
                m_lastResponse = Navigate(url, postData);
                m_responseCookies = m_lastResponse.Cookies;
                this.SaveResponseCookies(m_lastResponse.Cookies);
                using (System.IO.StreamReader sr = new System.IO.StreamReader(m_lastResponse.GetResponseStream(), m_encoding))
                {
                    res = sr.ReadToEnd();
                }
                return res;
            }
            finally
            {
                if (m_lastResponse != null)
                {
                    m_lastResponse.Close();
                }
            }
        }
        /// <summary>
        /// 访问网络，获取二进制的资源，并保存Cookie到实例
        /// </summary>
        /// <param name="url">资源的URL</param>
        /// <param name="postData">post方式提交时的数据</param>
        /// <returns>二进制数组形式的资源</returns>
        public byte[] NavigateBytes(string url, byte[] postData = null)
        {
            byte[] res = new byte[0];
            try
            {
                m_lastResponse = Navigate(url, postData);
                m_responseCookies = m_lastResponse.Cookies;
                this.SaveResponseCookies(m_lastResponse.Cookies);
                using (System.IO.BinaryReader br = new System.IO.BinaryReader(m_lastResponse.GetResponseStream()))
                {
                    res = br.ReadBytes((int)m_lastResponse.ContentLength);
                }
                return res;
            }
            finally
            {
                if (m_lastResponse != null)
                {
                    m_lastResponse.Close();
                }
            }
        }

        /// <summary>
        /// 获取二进制的PostData
        /// </summary>
        /// <param name="postData"></param>
        /// <returns></returns>
        public byte[] GetPostDataBytes(string postData)
        {
            byte[] res = new byte[0];
            if (!string.IsNullOrEmpty(postData))
            {
                res = m_encoding.GetBytes(postData);
            }
            return res;
        }
        /// <summary>
        /// 获得网页的提交信息
        /// </summary>
        /// <param name="postData">以字典的形式保存的提交数据</param>
        public byte[] GetPostDataBytes(Dictionary<string, string> postData)
        {
            string strPostData = "";
            foreach (KeyValuePair<string, string> kv in postData)
            {
                strPostData += "&" + System.Web.HttpUtility.UrlEncode(kv.Key) + "=" + System.Web.HttpUtility.UrlEncode(kv.Value) + "";
            }
            return GetPostDataBytes(strPostData.TrimStart('&'));
        }
        /// <summary>
        /// 根据最后一次响应获得的基础URL，获得将要跳转到的URL的正确路径
        /// </summary>
        /// <param name="url">目标URL</param>
        /// <returns>可访问的完整的URL</returns>
        public string GetActualURL(string url)
        {
            return GetActualURL(url, LastResponseBaseURL);
        }
        /// <summary>
        /// 清除Cookie
        /// </summary>
        public void ClearCookie()
        {
            m_requestCookies = new CookieCollection();
        }

        /// <summary>
        /// 将返回的Cookie添加到实例的Cookie集合中
        /// </summary>
        /// <param name="responseCookies">响应中包含的Cookie</param>
        protected void SaveResponseCookies(CookieCollection responseCookies)
        {
            foreach (System.Net.Cookie ck in responseCookies)
            {
                if (ck.Value.Length != 0)
                {
                    m_requestCookies.Add(ck);
                }
            }
        }
        /// <summary>
        /// 将实例的Cookie设置到请求对象中
        /// </summary>
        /// <param name="url">待请求的URL，用于设置Cookie的域</param>
        /// <param name="req">请求对象</param>
        protected void SetCookies(string url, HttpWebRequest req)
        {
            req.CookieContainer = new CookieContainer();
            System.Uri uri = new Uri(url);
            foreach (System.Net.Cookie ck in m_requestCookies)
            {
                ck.Domain = uri.Host;
                if (ck.Value.Length != 0)
                {
                    req.CookieContainer.Add(ck);
                }
            }
        }
        /// <summary>
        /// 给请求设置代理，当用户名和密码都存在时，对代理的访问将设置用户名和密码
        /// </summary>
        /// <param name="req">请求对象</param>
        protected void SetProxy(HttpWebRequest req)
        {
            if (!string.IsNullOrEmpty(m_proxy) && !string.IsNullOrEmpty(m_port))
            {
                WebProxy p = new WebProxy();
                try
                {
                    p.Address = new Uri(String.Format("http://{0}:{1}", m_proxy, m_port));
                    if (!string.IsNullOrEmpty(m_proxyName) && !string.IsNullOrEmpty(m_proxyPwd))
                    {
                        p.Credentials = new NetworkCredential(m_proxyName, m_proxyPwd);
                    }
                    req.Proxy = p;
                }
                catch { }
            }
        }
        /// <summary>
        /// 给请求设置提交数据
        /// </summary>
        /// <param name="postData">post方式提交的数据</param>
        /// <param name="req">请求对象</param>
        protected void SetPostData(byte[] postData, HttpWebRequest req)
        {
            if (postData != null && postData.Length > 0)
            {
                req.ContentType = "application/x-www-form-urlencoded";
                req.Method = "POST";
                req.ContentLength = postData.Length;
                using (System.IO.Stream sw = req.GetRequestStream())
                {
                    sw.Write(postData, 0, postData.Length);
                }
            }
        }
    }
}
